#-*- coding:utf8 -*-

# https://www.cnblogs.com/lichmama/p/3931212.html 本代码参考博客地址 他是基于2.x
# http://blog.csdn.net/CSDN_Boring/article/details/77542644 这是3.6的讲解
# http://www.ruanyifeng.com/blog/2017/05/websocket.html 软一峰的websocket概念见解
import threading
import hashlib
import socket
import base64
import struct
import math
import time
import socketUtils

global clients
clients = {}

# 通知客户端
def notify(message):

    for connection in clients.values():
        # message = "I'm WebSocket Client!" #'%c%c%s' % (0x81, len(message), message)
        message = message.encode('utf8')
        connection.send(message)

# 客户端处理线程
class websocket_thread(threading.Thread):
    def __init__(self, connection, username):
        super(websocket_thread, self).__init__()
        self.connection = connection
        self.username = username

    def run(self):

        data = self.connection.recv(2048)
        headers = self.parse_headers(data)
        token = self.generate_token(headers['Sec-WebSocket-Key'])
        token = token.decode('utf-8') #先转换成字符串

        msg = '\
HTTP/1.1 101 WebSocket Protocol Hybi-10\r\n\
Upgrade: WebSocket\r\n\
Connection: Upgrade\r\n\
Sec-WebSocket-Accept: %s\r\n\r\n' % token
        msg = msg.encode('utf8') #把整个header信息转换成二进制
        self.connection.send(msg) #和客户端进行握手

        while True:
            try:
                data = self.connection.recv(1024) #接收客户端信息
                #print("接收到客户端信息:",data,'time.clock()',time.clock(),'time.time()',time.time())
                if len(data)==0:
                    continue

            except socket.error as e:
                #print("------- unexpected error: ", e) #客户端断开
                self.connection.close()
                print('client ',self.username,' is close!')
                clients.pop(self.username)
                break
            data = self.parse_data(data) #解析客户端信息        
            if not data or len(data) == 0:
                continue                
            message = self.username + ":" + data            
            self.sendMessage(message)
            
    #发送websocket server报文部分(关键处理 python3需要对发送的消息进行二进制打包处理,之前一直卡在这里)
    def sendMessage(self, message):        
        msgLen = len(message)
        server_byte = bytes()
        server_byte+=struct.pack('h',199) 
        server_byte+=struct.pack('i',2000) 
        server_byte+=bytes(message,encoding='utf8')
        msgLen = len(server_byte)
        print("sendMessage : msgLen",msgLen)

        backMsgList = []

        '''
        当填写是129 只能发送文本数据发送二进制会报错Could not decode a text frame as UTF-8
        http://blog.csdn.net/yyf1990cs/article/details/50933720 
        http://blog.csdn.net/man_sion/article/details/77334822
        '''
        #http://www.cnblogs.com/hustskyking/p/websocket-with-node.html 细说websocket协议

        backMsgList.append(struct.pack('B', 130)) #写入固定的head 129(0x81 表示文本格式) 130(0x82 表示二进制)

        # https://docs.python.org/3.6/library/struct.html struct模块格式说明
        #对消息的固定处理方式开始:
        if msgLen <= 125: #报文长度小于126 直接写入长度
            backMsgList.append(struct.pack('b', msgLen))
        elif msgLen <=65535:#长度小于 2^16-1 需要填充一个字节的数字126 再写入报文长度
            backMsgList.append(struct.pack('b', 126)) #填充字节
            backMsgList.append(struct.pack('>H', msgLen)) #写入长度 大端格式的unsigned short(16位 2个字节)
        elif msgLen <= (math.pow(2,64)-1):#报文的最大长度2^64-1             
            backMsgList.append(struct.pack('b', 127)) #填充字节
            backMsgList.append(struct.pack('>Q', msgLen)) #写入长度 大端格式的 unsigned long long (64位 8个字节)
        else :
            print("the message is too long to send in a time")
            return
        #---对消息的固定处理方式结束---  

        message_byte = bytes()      
        for c in backMsgList:           
            message_byte += c  
        message_byte += server_byte     
        self.connection.send(message_byte)  

    
    #https://www.cnblogs.com/lidabo/p/6549778.html 服务器解析客户端数据更新详细的解析
    #解析客户端发送过来的数据:固定head， 报文长度L,  掩码M， 报文内容C
    def parse_data(self,data):
        raw_str = ""
        #第一个字节；第一位标识0或1是否接收完数据
        isFin = data[0] & 0x80 ==0x80 #是否接收完数据
        opcode = data[0] & 15 #数据类型
        print('parse_data--- opcode ----',opcode,'测试移位操作 fin',data[0]>>7,'isFin',isFin)
        if not isFin:
            return raw_str #还没接收完数据        
        isMask = data[1] & 0x80 == 0x80 #是否包含掩码 
        if not isMask:
            return raw_str #没有掩码 不处理数据
        if opcode==0x8:
            print('parse_data ... 客户端主动断开了....')
            return raw_str
        v = data[1] & 0x7f # 127

        #计算数据长度占用了多少字节
        if v == 0x7e:
            p = 4
        elif v == 0x7f:
            p = 10
        else:
            p = 2
        mask = data[p:p+4] #后四字节是掩码
        data = data[p+4:] #剩余的是数据内容        
        print('数据内容',len(data),data[0],data[1],type(data),type(data[0]))
        i = 0        
        protoId = bytes()
        msgByte = bytes()    
        for d in data:           
            code = d ^ mask[i%4] #通过掩码解码后的值是每个字节的值                 
            if i<4:#协议id 
                protoId += bytes.fromhex(socketUtils.intToHexStr(code)) #同16进制转换成bytes然后解压出int数值
            else:         
                #字符串信息，按照目前的处理方法 客户只能把字符串放在最后发送过来
                #而且服务器无法分字段处理它;比如客户端发送:用户名字符串和用户见解两个字段过来，就得拼接在一起作为一个字段发送过来
                msgByte += bytes.fromhex(socketUtils.intToHexStr(code))  #这样可以解决中文乱码      
                '''              
                char = chr(code)   
                print('code=',code,'d=',d,'char',char)      
                raw_str += char
                '''
            i+=1           
        protoId = struct.unpack('i',protoId)[0] 
        print("protoId=",protoId)            
        raw_str = msgByte.decode('utf8')
        print("raw_str=",raw_str)  
        return raw_str
    def parse_headers(self, msg):
        headers = {}
        msg = msg.decode('utf-8')
        header, data = msg.split('\r\n\r\n', 1)
        for line in header.split('\r\n')[1:]:
            key, value = line.split(': ', 1)
            headers[key] = value
        headers['data'] = data
        return headers

    def generate_token(self, msg):
        #通过客户端的 header 的Sec-WebSocket-Key和固定串生成Sec-WebSocket-Accept 进行握手
        key = msg + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' 
        key = key.encode("utf-8")
        ser_key = hashlib.sha1(key).digest()
        return base64.b64encode(ser_key)

# 服务端
class websocket_server(threading.Thread):
    def __init__(self, port):
        super(websocket_server, self).__init__()
        self.port = port

    def run(self):
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        ipPort = ('127.0.0.1', self.port)
        sock.bind(ipPort)
        sock.listen(5)
        print('websocket server started!',ipPort)
        while True:
            connection, address = sock.accept()
            try:
                username = "ID" + str(address[1])
                thread = websocket_thread(connection, username)
                thread.start()
                clients[username] = connection
                print("client ",username," in ")
            except socket.timeout as t:
                print('websocket connection timeout!')



import urllib
#import requests
if __name__ == '__main__':
    '''
    s = b'\x88\x9a\x0fN\xee\x1a\x0c\xa4\xb9\x7fm\x1d\x81yd+\x9a:_<\x81n`-\x81v/\x0b\x9ch`<'
    把 s 转换成utf字符串发办法 
    #https://stackoverflow.com/questions/26150568/unicodedecodeerror-utf8-codec-cant-decode-byte-0x80-in-position-0-invalid-s

    encoded = base64.b64encode(s).decode("utf8")
    print('encoded',encoded)   
    decoded = urllib.request.unquote(encoded)    
    print(decoded)
    '''
    server = websocket_server(9000)
    server.start()
